发票 OCR 识别率低?试试这 7 个优化技巧

大家好,我是正在实战各种 AI 项目的程序员晚枫。


📉 识别率从 95% 到 99%,我做了这些

3 个月前

刚开始用 OCR 识别发票。

官方说:准确率 98%+。

我实测:95%,有时候 90% 都不到。

财务姐姐:"你这 95%,100 张错 5 张,我还得手动改,有啥用?"

:"……我优化。"

现在

稳定在 99%+,1000 张错不到 10 张。

今天:把 7 个优化技巧分享给你。


🎯 技巧 1:优先用 PDF 电子票

测试数据

发票类型识别率建议
PDF 电子票99.5%✅ 强烈推荐
扫描全能王 JPG98.8%✅ 推荐
手机拍照 JPG96.5%⭐ 可以
模糊拍照92.3%❌ 不推荐

结论

  • PDF 电子票识别率最高
  • 能要电子版,就别要纸质版
  • 纸质版用扫描 APP,别直接拍照

实施建议

1
2
3
通知供应商:
"请优先发送电子发票 PDF 版本,
纸质发票请用扫描全能王扫描后发送。"

🎯 技巧 2:制定拍照规范

如果必须拍照,制定规范:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
📋 发票拍照规范

✅ 要做:
1. 光线充足(白天窗边或台灯下)
2. 发票平整(用书本压平)
3. 正对拍摄(手机与发票平行)
4. 四角完整(确保发票 4 个角都在画面内)
5. 使用扫描 APP(扫描全能王、微软 Lens)

❌ 不要:
1. 光线暗(晚上不开灯)
2. 发票皱巴巴
3. 斜着拍
4. 只拍一部分
5. 有反光遮挡文字

效果

  • 拍照发票识别率:92% → 97%
  • 财务姐姐拍照认真了
  • 我不崩溃了

🎯 技巧 3:图片预处理

拍照后的图片,先处理再识别:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
import cv2
import numpy as np

def preprocess_image(image_path, output_path):
"""图片预处理"""
img = cv2.imread(image_path)

# 1. 转为灰度图
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

# 2. 去噪
denoised = cv2.fastNlMeansDenoising(gray, None, 30, 7, 21)

# 3. 增强对比度
clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
enhanced = clahe.apply(denoised)

# 4. 二值化(可选)
_, binary = cv2.threshold(enhanced, 0, 255,
cv2.THRESH_BINARY + cv2.THRESH_OTSU)

# 保存
cv2.imwrite(output_path, binary)
print(f"✅ 图片预处理完成:{output_path}")

# 使用
preprocess_image('invoice.jpg', 'invoice_processed.jpg')

效果

  • 模糊图片变清晰
  • 光线暗的变亮
  • 识别率提升 2-3%

🎯 技巧 4:添加质量检查

识别前检查图片质量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def check_image_quality(image_path):
"""检查图片质量"""
img = cv2.imread(image_path)
gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)

checks = {}

# 1. 检查亮度
brightness = np.mean(gray)
checks['brightness'] = brightness
if brightness < 80:
return False, f"图片太暗(亮度:{brightness:.1f})"

# 2. 检查清晰度
variance = cv2.Laplacian(gray, cv2.CV_64F).var()
checks['sharpness'] = variance
if variance < 100:
return False, f"图片模糊(清晰度:{variance:.1f})"

# 3. 检查尺寸
height, width = gray.shape
checks['size'] = (width, height)
if width < 800 or height < 600:
return False, f"图片太小({width}x{height})"

return True, "合格"

# 使用
is_ok, msg = check_image_quality('invoice.jpg')
if not is_ok:
print(f"❌ {msg},请重新拍照")
else:
print("✅ 图片质量合格")

效果

  • 低质量图片被拦截
  • 减少识别失败
  • 整体识别率提升

🎯 技巧 5:分批处理 + 重试

批量处理时分小批

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
def process_in_batches(files, batch_size=50):
"""分批处理"""
total = len(files)
success = 0
failed = []

for i in range(0, total, batch_size):
batch = files[i:i+batch_size]
print(f"📦 处理第{i//batch_size + 1}批({len(batch)}张)")

for file_path in batch:
ok, error = recognize_with_retry(file_path)
if ok:
success += 1
else:
failed.append((file_path, error))

# 每批之间休息 2 秒,避免限流
time.sleep(2)

return success, failed

def recognize_with_retry(file_path, max_retries=3):
"""带重试的识别"""
for i in range(max_retries):
try:
# 识别逻辑
return True, None
except Exception as e:
if i < max_retries - 1:
time.sleep(5) # 5 秒后重试
else:
return False, str(e)

效果

  • 避免网络超时
  • 避免 API 限流
  • 识别率提升 1-2%

🎯 技巧 6:后处理校验

识别后校验关键字段

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import re

def validate_invoice_data(data):
"""校验发票数据"""
errors = []

# 1. 校验发票代码(10 位或 12 位数字)
if not re.match(r'^\d{10}|\d{12}$', data['发票代码']):
errors.append(f"发票代码格式错误:{data['发票代码']}")

# 2. 校验发票号码(8 位数字)
if not re.match(r'^\d{8}$', data['发票号码']):
errors.append(f"发票号码格式错误:{data['发票号码']}")

# 3. 校验金额(数字)
try:
amount = float(data['金额'])
if amount < 0:
errors.append(f"金额为负数:{amount}")
except:
errors.append(f"金额格式错误:{data['金额']}")

# 4. 校验日期
try:
datetime.strptime(data['开票日期'], '%Y-%m-%d')
except:
errors.append(f"日期格式错误:{data['开票日期']}")

return len(errors) == 0, errors

# 使用
is_valid, errors = validate_invoice_data(invoice_data)
if not is_valid:
print(f"⚠️ 数据校验失败:{errors}")
# 标记为需要人工复核

效果

  • 自动发现识别错误
  • 减少错误数据流入
  • 财务工作量减少

🎯 技巧 7:建立异常发票库

记录识别失败的发票

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import json

def log_failed_invoice(filename, error, image_path):
"""记录失败发票"""
log_entry = {
'filename': filename,
'error': error,
'image_path': image_path,
'time': datetime.now().isoformat(),
'status': 'pending' # pending/fixed/ignored
}

# 追加到日志文件
log_file = './failed_invoices.json'
if os.path.exists(log_file):
with open(log_file, 'r', encoding='utf-8') as f:
logs = json.load(f)
else:
logs = []

logs.append(log_entry)

with open(log_file, 'w', encoding='utf-8') as f:
json.dump(logs, f, ensure_ascii=False, indent=2)

# 定期分析失败原因
def analyze_failures():
"""分析失败原因"""
with open('./failed_invoices.json', 'r', encoding='utf-8') as f:
logs = json.load(f)

error_types = {}
for log in logs:
error_type = log['error'].split(':')[0]
error_types[error_type] = error_types.get(error_type, 0) + 1

print("失败原因统计:")
for error_type, count in sorted(error_types.items(), key=lambda x: -x[1]):
print(f" {error_type}: {count}")

效果

  • 知道哪些发票总失败
  • 针对性优化
  • 持续改进识别率

📊 优化效果对比

优化技巧识别率提升实施难度
1. 优先用 PDF+3%⭐ 简单
2. 拍照规范+2%⭐⭐ 中等
3. 图片预处理+1.5%⭐⭐ 中等
4. 质量检查+1%⭐ 简单
5. 分批 + 重试+1%⭐⭐ 中等
6. 后处理校验+0.5%⭐⭐⭐ 较难
7. 异常发票库+0.5%⭐⭐ 中等
总计+9.5%-

**从 95% 到 99.5%**,就靠这 7 个技巧。


💬 联系我

平台账号/链接
微信扫码加好友
微博@程序员晚枫
知乎@程序员晚枫
抖音@程序员晚枫
小红书@程序员晚枫
B 站Python 自动化办公社区

主营业务:AI 编程培训、企业内训、技术咨询


🎓 推荐课程


95% 到 99%,看起来只提升了 4%。

但意味着:1000 张发票,错误从 50 张降到 10 张。

财务姐姐的工作量:从 1 小时降到 10 分钟。

这就是优化的价值。

细节决定成败。 💪


完整代码已上传 GitHub,扫码获取。

🎓 AI 编程实战课程

想系统学习 AI 编程?程序员晚枫的 AI 编程实战课 帮你从零上手!